/*
 * Copyright (C) 2012 Richard Schilling
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package dalvik.vm.analysis;

import static dalvik.vm.analysis.CodeVerifyConstants.kInsnFlagBranchTarget;
import static dalvik.vm.analysis.CodeVerifyConstants.kInsnFlagChanged;
import static dalvik.vm.analysis.CodeVerifyConstants.kInsnFlagGcPoint;
import static dalvik.vm.analysis.CodeVerifyConstants.kInsnFlagInTry;
import static dalvik.vm.analysis.CodeVerifyConstants.kInsnFlagVisited;
import static dalvik.vm.analysis.CodeVerifyConstants.kInsnFlagWidthMask;

import com.android.dx.io.instructions.DecodedInstruction;
import com.android.dx.io.instructions.ShortArrayCodeOutput;

import dalvik.vm.analysis.AnalyzableInstruction.RegisterLine;
import dalvik.vm.util.SparseArray;



/**
 * A list of instructions.
 * <p>
 * This corresponds to the instructions that are found in a DEX
 * <code>Code</code> object. The key to the sparse array is the instruction
 * address.
 * 
 * @author rschilling
 */
public class InstructionList extends SparseArray<AnalyzableInstruction> {

    /**
     * Extracts the instruction flags for all instructions and returns them.
     * 
     * @return all instruction flags;
     */
    public int[] getInsnFlags() {

        int[] result = new int[size()];

        for (int i = 0; i < size(); i++) {
            result[i] = valueAt(i).insnFlags;
        }

        return result;

    }

    /**
     * Returns "true" if the flags indicate that this address holds the start of
     * an instruction.
     */
    boolean dvmInsnIsOpcode(int addr) {
        return (get(addr).insnFlags & kInsnFlagWidthMask) != 0;
    }

    /**
     * Extract the unsigned 16-bit instruction width from "flags".
     */
    int dvmInsnGetWidth(int addr) {
        return get(addr).insnFlags & kInsnFlagWidthMask;
    }

    /*
     * Changed?
     */
    boolean dvmInsnIsChanged(int addr) {
        return (get(addr).insnFlags & kInsnFlagChanged) != 0;
    }

    void dvmInsnSetChanged(int addr, boolean changed) {
        if (changed)
            get(addr).insnFlags |= kInsnFlagChanged;
        else
            get(addr).insnFlags &= ~kInsnFlagChanged;
    }

    /*
     * Visited?
     */
    boolean dvmInsnIsVisited(int addr) {
        return (get(addr).insnFlags & kInsnFlagVisited) != 0;
    }

    void dvmInsnSetVisited(int addr, boolean changed) {
        if (changed)
            get(addr).insnFlags |= kInsnFlagVisited;
        else
            get(addr).insnFlags &= ~kInsnFlagVisited;
    }

    /*
     * Visited or changed?
     */
    boolean dvmInsnIsVisitedOrChanged(int addr) {
        return (get(addr).insnFlags & (kInsnFlagVisited | kInsnFlagChanged)) != 0;
    }

    /*
     * In a "try" block?
     */
    boolean dvmInsnIsInTry(int addr) {
        return (get(addr).insnFlags & kInsnFlagInTry) != 0;
    }

    void dvmInsnSetInTry(int addr, boolean inTry) {
        if (inTry)
            get(addr).insnFlags |= kInsnFlagInTry;
        else
            get(addr).insnFlags &= ~kInsnFlagInTry;
    }

    /*
     * AnalyzableInstruction is a branch target or exception handler?
     */
    boolean dvmInsnIsBranchTarget(int addr) {
        return (get(addr).insnFlags & kInsnFlagBranchTarget) != 0;
    }

    void dvmInsnSetBranchTarget(int addr, boolean isBranch) {
        if (isBranch)
            get(addr).insnFlags |= kInsnFlagBranchTarget;
        else
            get(addr).insnFlags &= ~kInsnFlagBranchTarget;
    }

    /*
     * AnalyzableInstruction is a GC point?
     */
    boolean dvmInsnIsGcPoint(int addr) {
        return (get(addr).insnFlags & kInsnFlagGcPoint) != 0;
    }

    void dvmInsnSetGcPoint(int addr, boolean isGcPoint) {

        if (isGcPoint)
            get(addr).insnFlags |= kInsnFlagGcPoint;
        else
            get(addr).insnFlags &= ~kInsnFlagGcPoint;
    }

    private RegisterTable registerTable;

    public RegisterTable getRegisterTable() {
        if (registerTable == null)
            registerTable = new RegisterTable();
        return registerTable;
    }

    public static InstructionList allocate(DecodedInstruction[] instructions) {

        InstructionList result = new InstructionList();

        if (instructions == null)
            return result;

        // as a first guess, I am going to assume that the address is the
        // position in the array

        for (int i = 0; i < instructions.length; i++) {
            if (instructions[i] == null)
                continue;
            result.put(i, new AnalyzableInstruction(instructions[i]));
        }

        return result;

    }

    public static InstructionList allocate(short[] instructions) {

        if (instructions == null)
            return new InstructionList(); // m-t

        return allocate(DecodedInstruction.decodeAll(instructions));
    }

    public short[] decode() {

        // a single instruction should never take more than
        // AnalyzableInstruction.MAX_SIZE shorts.
        ShortArrayCodeOutput result = new ShortArrayCodeOutput(keyAt(size() - 1)
                + AnalyzableInstruction.MAX_SIZE);

        for (int i = 0; i < size(); i++) {

            DecodedInstruction dIns = valueAt(i).asDecodedInstruction();
            dIns.encode(result);

        }

        return result.getArray();
    }

    /**
     * The register table for this instruction list.
     * 
     * @author rschilling
     */
    public class RegisterTable {

        public SparseArray<RegisterLine> registerTable() {
            SparseArray<RegisterLine> result = new SparseArray<RegisterLine>();
            for (int i = 0; i < size(); i++) {
                if (valueAt(i).hasRegisterLine()) {

                    result.put(keyAt(i), valueAt(i).getRegisterLine());
                }
            }

            return result;
        }

        /**
         * Number of registers we track for each instruction. This is equal
         * to the method's declared "registersSize" plus kExtraRegs.
         */
        public int insnRegCountPlus;

        /**
         * Storage for a register line we're currently working on.
         */
        public RegisterLine workLine;

        /**
         * Storage for a register line we're saving for later.
         */
        public RegisterLine savedLine;

    }

}
